package com.gulimall.ware.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.common.to.OrderTo;
import com.common.to.SkuInfoTo;
import com.common.to.WareLockTo;
import com.common.to.mq.StockDetailTo;
import com.common.to.mq.StockLockedTo;
import com.common.utils.R;
import com.gulimall.ware.dao.WareOrderTaskDao;
import com.gulimall.ware.dao.WareOrderTaskDetailDao;
import com.gulimall.ware.entity.WareOrderTaskDetailEntity;
import com.gulimall.ware.entity.WareOrderTaskEntity;
import com.gulimall.ware.exception.StockLockException;
import com.gulimall.ware.feign.OrderFeignService;
import com.gulimall.ware.feign.ProductFeignService;
import com.rabbitmq.client.Channel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.core.RabbitMessageOperations;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.common.utils.PageUtils;
import com.common.utils.Query;

import com.gulimall.ware.dao.WareSkuDao;
import com.gulimall.ware.entity.WareSkuEntity;
import com.gulimall.ware.service.WareSkuService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;

import javax.annotation.Resource;

@Slf4j
@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Resource
    private WareOrderTaskDao wareOrderTaskDao;
    @Resource
    private WareOrderTaskDetailDao wareOrderTaskDetailDao;
    @Resource
    private ProductFeignService productFeignService;
    @Resource
    private WareSkuDao wareSkuDao;
    @Resource
    private RabbitTemplate rabbitTemplate;
    @Resource
    private OrderFeignService orderFeignService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        QueryWrapper<WareSkuEntity> queryWrapper = new QueryWrapper<>();
        String skuId = (String) params.get("skuId");
        if(StringUtils.isNotBlank(skuId)){
            queryWrapper.eq("sku_id",skuId);
        }
        String wareId = (String) params.get("wareId");
        if(StringUtils.isNotBlank(wareId)){
            queryWrapper.eq("ware_id",wareId);
        }
        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params),
                queryWrapper
        );

        return new PageUtils(page);
    }

    @Override
    public void addStock(Long skuId, Long wareId, Integer skuNum) {
        List<WareSkuEntity> wareSkuEntityList = baseMapper.selectList(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
        if(wareSkuEntityList.size() <= 0){
            // 如果不存在指定的仓储，就新建
            WareSkuEntity wareSkuEntity = new WareSkuEntity();
            wareSkuEntity.setSkuId(skuId);
            R r = productFeignService.info(skuId);
            if(r.getCode() != 0){
                log.error("调用商品服务接口查询sku信息失败");
                return;
            }
            wareSkuEntity.setSkuName(((SkuInfoTo)r.get("skuInfo")).getSkuName());
            wareSkuEntity.setWareId(wareId);
            wareSkuEntity.setStock(skuNum);
            wareSkuEntity.setStockLocked(0);
            baseMapper.insert(wareSkuEntity);
        }else{
            // 如果存在就在原有的库存上添加
            baseMapper.addStock(skuId,wareId,skuNum);
        }
    }

    @Override
    public Map<Long, Integer> getStockList(List<Long> skuIdList) {
        Map<Long, Integer> stockMap = new HashMap<>();
        for (Long skuId : skuIdList) {
            Integer stock = wareSkuDao.getStockListBySkuIdList(skuId);
            stockMap.put(skuId,stock);
        }
        return stockMap;
    }

    /**
     * 锁库操作
     * @param wareLockTo
     * @return
     */
    // @Transactional可以指定特定的异常回滚，默认是所有的运行时异常都会回滚
    @Transactional(rollbackFor = StockLockException.class)
    @Override
    public Boolean wareLock(WareLockTo wareLockTo) {
        // 1、添加锁库存订单记录
        WareOrderTaskEntity wareOrderTaskEntity = new WareOrderTaskEntity();
        wareOrderTaskEntity.setOrderId(wareLockTo.getOrderId());
        wareOrderTaskEntity.setOrderSn(wareLockTo.getOrderSn());
        wareOrderTaskEntity.setCreateTime(new Date());
        wareOrderTaskDao.insert(wareOrderTaskEntity);
        // 2、计算可用仓库ID与skuId关联对象集合
        List<AvailWare> availWareList = wareLockTo.getWareLockList().stream().map(wareLock -> {
            // 分别计算各个sku下可用的仓库ID集合
            List<Long> wareIdList = wareSkuDao.getAvailWareIdList(wareLock.getSkuId(), wareLock.getSkuQuantity());
            return new AvailWare(wareLock.getSkuId(), wareLock.getSkuQuantity(), wareIdList);
        }).collect(Collectors.toList());

        // 3、寻找可用库存锁定，如果没有库存就抛异常，全部回滚
        for (AvailWare availWare : availWareList) {
            boolean isLock = false;
            Long skuId = availWare.getSkuId();
            Integer num = availWare.getNum();
            for (Long wareId : availWare.getWareIdList()) {
                int i = wareSkuDao.lockStock(skuId, wareId, num);
                if(i > 0){
                    // 大于0说明锁库成功，直接锁下一个sku
                    // 添加锁库订单项记录
                    WareOrderTaskDetailEntity taskDetailEntity = WareOrderTaskDetailEntity.builder()
                            .skuId(skuId)
                            .skuName("")
                            .skuNum(num)
                            .taskId(wareOrderTaskEntity.getId())
                            .wareId(wareId)
                            .lockStatus(1)
                            .build();
                    wareOrderTaskDetailDao.insert(taskDetailEntity);
                    // 随后通知MQ
                    StockLockedTo lockedTo = new StockLockedTo();
                    lockedTo.setId(wareOrderTaskEntity.getId());
                    StockDetailTo detailTo = new StockDetailTo();
                    BeanUtils.copyProperties(taskDetailEntity,detailTo);// 这里直接存entity。但是应该存id更好，数据最好来自DB
                    lockedTo.setDetailTo(detailTo);
                    rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",lockedTo);

                    isLock = true;
                    break;
                }
            }
            if(!isLock){
                throw new StockLockException(skuId);
            }
        }
        return true;
    }


    /**
     * MQ客户端接收延时队列消息处理：解锁库存
     * @param wareOrderTaskEntity
     * @param taskDetailEntity
     * @param message
     * @param channel
     * 库存发生异常的两个场景：
     *         1、未找到库存，锁库存失败。这种情况会抛出异常，所以整个方法事务会回滚，那么对应的锁库存记录也就没有，那么其他成功锁库存的也会回滚，也就无需解锁库存
     *         2、找到库存，说明锁库存成功，但是在主业务事务中的其他操作抛出异常，导致锁库存未回滚，所以需要解锁库存
     */
    @Override
    public void releaseStock(WareOrderTaskEntity wareOrderTaskEntity,WareOrderTaskDetailEntity taskDetailEntity, Message message, Channel channel) throws IOException {
        // 1、先查询是否存在对应的 wms_ware_order_task 记录，如果存在说明整个库存操作都回滚了，无需解锁库存
        if(wareOrderTaskEntity != null){
            // 如果存在，再判断当前订单是否是已取消状态，如果是就解锁库存，如果不是取消状态，就跳过
            R info = orderFeignService.getStatus(wareOrderTaskEntity.getOrderId());
            if(info.getCode() == 0){
                // 调用成功
                OrderTo order = info.getData("order",new TypeReference<OrderTo>() {});
                if(order == null || order.getStatus() == 4){        // 主业务操作订单数据回滚或者订单被取消
                    // 进行解库存操作
                    if(taskDetailEntity.getLockStatus() == 1){
                        // 当前库存记录是锁定状态才对库存进行解锁操作，防止多次解锁使库存数据对不上
                        wareSkuDao.releaseStock(taskDetailEntity.getSkuId(),taskDetailEntity.getSkuNum(),taskDetailEntity.getWareId());
                    }
                    // 最后将库存记录的状态设为已解锁
                    wareOrderTaskDetailDao.changeLockStatus(taskDetailEntity.getId(),2);
                }
            }else{
                // 远程调用失败就将消息重新放入消息队列
                throw new RuntimeException("远程调用失败");
            }
        }
    }


}

@AllArgsConstructor
@Data
class AvailWare{
    private Long skuId;
    private Integer num;
    private List<Long> wareIdList;
}